import {
  CENTEREVENTS,
  EventsRes,
  KEYBOARD_KEY,
  ObjectTreeMenuUtils,
  STATE,
  SceneGraph,
} from '@amcax/base';
import { EVENT_CENTER_MANAGER_EVENTS } from '@app-cad/common/Constants';
import { cmdMgr } from '@app-cad/manager/CmdManager';
import { ctx2, docMgr, previewer, userEvents } from '@app-cad/module';
import { wsApi } from '@app-cad/server';
import { useMemoizedFn } from 'ahooks';
import throttle from 'lodash/throttle';
import { useCallback, useEffect, useRef, useState } from 'react';
import { BaseEventCenter } from '../events/BaseEventCenter';
import { csEvent } from '../events/EventCenter';
import { previewData } from '../utils/render';
import { useTools } from './useTools';

const createOrEdit = <CommandParamType = any>(
  commandName: string,
  params: CommandParamType,
  preview = false,
  returnFullResponse = false,
  editTargetDxid?: string,
) => {
  if (editTargetDxid == null) {
    return wsApi.create(
      { c: commandName, ...params },
      preview,
      returnFullResponse,
    );
  }

  return wsApi.execCmd(
    { c: 'SetParams', ...params, o: editTargetDxid, p: preview },
    returnFullResponse,
  );
};

export type PreviewDataModifier = (data: any, profile: any) => [any, any];

export const useCommand = <CommandParamType = any>(
  id: string,
  {
    commandName,
    editDxid,
    dependencies = [],
    releaseObjects,
    onDestroy,
    onClose,
    finishDraw,
    clearCanvasSelection = true,
    previewDataModifier,
  }: {
    commandName?: string;
    editDxid?: string;
    dependencies?: string[];
    releaseObjects?: VoidFunction;
    onDestroy?: VoidFunction;
    onClose?: VoidFunction;
    finishDraw?: () => Promise<any>;
    clearCanvasSelection?: boolean;
    previewDataModifier?: PreviewDataModifier;
  } = {},
) => {
  const [initingEdit, setInitingEdit] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [isPreviewLoading, setIsPreviewLoading] = useState(false);
  const [previewCmdResult, setPreviewCmdResult] = useState<any>(null);
  const previewRequestId = useRef<symbol>();

  const destroyedFlag = useRef(false);
  // 存储register时的eventCenter，防止注册和注销事件时 csEvent不同
  const registeredEventCenter = useRef<BaseEventCenter>();

  const { picker, renderer } = useTools();

  const referenceObjectsShownTemporarily = useRef<Set<string>>(new Set());

  useEffect(() => {
    registerEvents();

    if (clearCanvasSelection) {
      picker
        .clearSelectedModel()
        .clearSelectedMesh()
        .clearSelectedLine()
        .clearSelectedVertex();
    }

    userEvents
      .on(EVENT_CENTER_MANAGER_EVENTS.EVENT_CENTER_CHANGING, unregisterEvents)
      .on(EVENT_CENTER_MANAGER_EVENTS.EVENT_CENTER_CHANGED, registerEvents);

    return onCmdExit;
  }, []);

  useEffect(() => {
    if (dependencies.length === 0) {
      return;
    }
    showObjectTemporarily(dependencies).then();

    return () => restoreObjectVisibility(dependencies);
  }, [dependencies]);

  useEffect(() => {
    if (editDxid == null) {
      return;
    }
    hideObjectTemporarily([editDxid]);

    return () => restoreObjectVisibility([editDxid]);
  }, [editDxid]);

  const _release = () => {
    const { assisGroup } = ctx2.cadScene;

    assisGroup.clear();
    SceneGraph.emptyGroup(assisGroup);
    previewer.clear();
    ctx2.viewer.requestRender();
  };

  const onCmdExit = () => {
    unregisterEvents();

    userEvents
      .off(EVENT_CENTER_MANAGER_EVENTS.EVENT_CENTER_CHANGING, unregisterEvents)
      .off(EVENT_CENTER_MANAGER_EVENTS.EVENT_CENTER_CHANGED, registerEvents);

    _release();
    releaseObjects?.();
    onDestroy?.();

    destroyedFlag.current = true;
  };

  const _finishDraw = useMemoizedFn(finishDraw ?? (async () => {}));
  const isFinishingDrawRef = useRef(false);

  const onKeydown = useCallback(async (e: EventsRes<KeyboardEvent>) => {
    if (e.key === KEYBOARD_KEY.ESC) {
      onClose?.();
    } else if (e.key === KEYBOARD_KEY.ENTER) {
      if (isFinishingDrawRef.current) {
        return;
      }
      isFinishingDrawRef.current = true;
      try {
        await _finishDraw();
      } finally {
        // in case finishDraw does not close the command
        isFinishingDrawRef.current = false;
      }
    }
  }, []);

  // ========= 鼠标和键盘事件=========
  const registerEvents = () => {
    registeredEventCenter.current = csEvent();
    registeredEventCenter.current
      .setState(id)
      .on(CENTEREVENTS.KEYDOWN, onKeydown)
      .addExit(onCmdExit);
  };

  const unregisterEvents = () => {
    if (registeredEventCenter.current.state !== STATE.SLEEPING) {
      registeredEventCenter.current
        .off(CENTEREVENTS.KEYDOWN, onKeydown)
        .setDefault();
    }
  };

  const initEditDependency = async (pickIds: string[]) => {
    if (!pickIds?.some((i) => i != null)) {
      return;
    }

    setInitingEdit(true);
    try {
      await cmdMgr.fetchDependentShapeInfo(pickIds);
    } finally {
      setInitingEdit(false);
    }
  };

  const previewInCommand = (modelsData: any, profile?: any) => {
    if (destroyedFlag.current) {
      return Promise.resolve();
    }
    return previewData(modelsData, profile);
  };

  const confirmCommand = async (
    params: CommandParamType,
    preview = false,
    errCallback: (err: any) => void = () => {},
  ) => {
    let currentRequestId: symbol;

    if (preview) {
      previewRequestId.current = Symbol();
      currentRequestId = previewRequestId.current;

      setPreviewCmdResult(null);
      setIsPreviewLoading(true);
    } else {
      setIsLoading(true);
    }

    let res: any;

    try {
      res = await createOrEdit(commandName, params, preview, true, editDxid);

      if (
        preview &&
        !destroyedFlag.current &&
        currentRequestId === previewRequestId.current
      ) {
        setPreviewCmdResult(res);

        if (previewDataModifier) {
          await previewInCommand(...previewDataModifier(res.data, res.Profile));
        } else {
          await previewInCommand(res.data, res.Profile);
        }
      }
    } catch (err: any) {
      if (currentRequestId === previewRequestId.current) {
        if (preview) {
          previewer.clear();
        }

        errCallback?.(err);
      }
      // 抓到err 防止直接崩溃
    } finally {
      setIsLoading(false);
      setIsPreviewLoading(false);
    }

    return res;
  };

  const confirmPreviewCommand = useRef(
    throttle(
      (params: CommandParamType, errCallback: (err: any) => void = () => {}) =>
        confirmCommand(params, true, errCallback),
      300,
      {
        leading: false,
      },
    ),
  ).current;

  // need initEditDependency first
  const showObjectTemporarily = async (ids: string[]) => {
    ids.forEach((id: string) => {
      const target = docMgr.getDocumentObjectById(id);
      if (
        ObjectTreeMenuUtils.initialNodeTypes.includes(target?.type) &&
        !target?.visibility
      ) {
        docMgr.setLocalReferenceVisibility(id, true);
        referenceObjectsShownTemporarily.current.add(id);
      }
    });

    const unRendered = ids.filter((id) => {
      const {
        modelMesh,
        modelEdge,
        modelVertex,
        pickMesh,
        pickEdge,
        pickVertex,
        hiddenEdge,
        outlineMesh,
        outlineEdge,
      } = renderer.getModelByDxid(id);

      return (
        modelMesh == null &&
        modelEdge == null &&
        modelVertex == null &&
        pickMesh == null &&
        pickEdge == null &&
        pickVertex == null &&
        hiddenEdge == null &&
        outlineMesh == null &&
        outlineEdge == null
      );
    });

    if (unRendered.length > 0) {
      await renderer.drawNewModels(unRendered);
    }

    renderer.setModelsVisible(ids, true);

    ctx2.viewer.requestRender();
  };

  const hideObjectTemporarily = (ids: string[]) => {
    renderer.setModelsVisible(ids, false);

    ctx2.viewer.requestRender();
  };

  const restoreObjectVisibility = (ids: string[]) => {
    const shows: string[] = [];
    const hides: string[] = [];

    ids.forEach((id) => {
      const target = docMgr.getDocumentObjectById(id);

      if (target == null) {
        return;
      }

      if (referenceObjectsShownTemporarily.current.has(id)) {
        docMgr.setLocalReferenceVisibility(id, false);
        return;
      }

      if (target.isRecursiveVisible) {
        shows.push(id);
      } else {
        hides.push(id);
      }
    });

    renderer.setModelsVisible(shows, true);
    renderer.setModelsVisible(hides, false);

    ctx2.viewer.requestRender();
  };

  return {
    initingEdit,
    initEditDependency,
    confirmCommand,
    confirmPreviewCommand,
    previewCmdResult,
    clear: () => {
      previewer.clear();
    },
    isLoading,
    isPreviewLoading,
    showObjectTemporarily,
    hideObjectTemporarily,
    restoreObjectVisibility,
    destroyedFlag,
    previewInCommand,
  };
};
